﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Workflow.ComponentModel;
using System.Workflow.ComponentModel.Compiler;

using Microsoft.SharePoint;
using Microsoft.SharePoint.Workflow;
using Microsoft.SharePoint.WorkflowActions;

using PublishingWorkflowAction.PublicationService;

namespace PublishingWorkflowAction
{

    /// <summary>
    /// Publishing Action
    /// Custom workflow action to publish a SharePoint list item to a MASAS Hub.
    /// </summary>
    public class PublishingAction : Activity
    {

        // MASAS List Names...
        private const string _masasHubConnectionsListName   = "MASAS Hub Connections";

        // SharePoint Internal field names...
        private const string _spTitleField                  = "Title";
        private const string _spIDField                     = "ID";

        // MASAS Hub Connections field names...
        private const string _masasURIField                 = "URI";
        private const string _masasTokenField               = "Token";
        private const string _masasAccessRightsField        = "AccessRights";
        private const string _masasLastAccessedField        = "LastAccessed";

        // MASAS Hub Item field names...
        private const string _masasMessageStatusField   = "MessageStatus";
        private const string _masasSymbolIDField        = "SymbolID";
        private const string _masasExpirationField      = "Expiration";
        private const string _masasLatitudeField        = "Latitude";
        private const string _masasLongitudeField       = "Longitude";
        private const string _masasSourceItemIDField    = "SourceItemID";
        private const string _masasLastUpdatedDTGField  = "LastUpdatedDTG";
        private const string _masasContentField         = "Content";
        private const string _masasSummaryField         = "Summary";
        private const string _masasCategoryField        = "Category";

        // Error messages...
        private const string _msgNoHubFound         = "The selected hub could not be found.";
        private const string _msgReadOnlyHubFound   = "The selected hub is Read-Only.";
        private const string _msgNoHubListFound     = "The MASAS Hub Connections List could not be found.";
        private const string _msgInvalidHubList     = "The selected MASAS Hub List is invalid.";
        private const string _msgMissingData        = "Data is missing for these fields: ";

        // Dependency Properties...
        public static DependencyProperty __ContextProperty = DependencyProperty.Register( "__Context", typeof( WorkflowContext ), typeof( PublishingAction ) );
        public static DependencyProperty ListIdProperty = DependencyProperty.Register( "ListId", typeof( string ), typeof( PublishingAction ) );
        public static DependencyProperty ListItemProperty = DependencyProperty.Register( "ListItem", typeof( int ), typeof( PublishingAction ) );
        public static DependencyProperty HubListIdProperty = DependencyProperty.Register( "HubListId", typeof( string ), typeof( PublishingAction ) );
        public static DependencyProperty HubListItemProperty = DependencyProperty.Register( "HubListItem", typeof( int ), typeof( PublishingAction ) );
        public static DependencyProperty PublicationResultProperty = DependencyProperty.Register( "PublicationResult", typeof( bool ), typeof( PublishingAction ) );
        public static DependencyProperty PublicationResultMsgProperty = DependencyProperty.Register( "PublicationResultMsg", typeof( string ), typeof( PublishingAction ) );

        // Wokflow action properties available to the designer...
        
        /// <summary>
        /// Gets or sets the workflow context.
        /// </summary>
        /// <value>
        /// The workflow context.
        /// </value>
        [Description( "Current workflow context." )]
        [Category( "MASAS Publication Action" )]
        [Browsable( true )]
        [DesignerSerializationVisibility( DesignerSerializationVisibility.Visible )]
        public WorkflowContext __Context
        {
            get
            {
                return ( ( WorkflowContext )( base.GetValue( PublishingAction.__ContextProperty ) ) );
            }
            set
            {
                base.SetValue( PublishingAction.__ContextProperty, value );
            }
        }

        /// <summary>
        /// Gets or sets the current list id.
        /// </summary>
        /// <value>
        /// The current list id.
        /// </value>
        [Description( "Current list ID." )]
        [Category( "MASAS Publication Action" )]
        [Browsable( true )]
        [DesignerSerializationVisibility( DesignerSerializationVisibility.Visible )]
        public string ListId
        {
            get
            {
                return ( ( string )( base.GetValue( PublishingAction.ListIdProperty ) ) );
            }
            set
            {
                base.SetValue( PublishingAction.ListIdProperty, value );
            }
        }

        /// <summary>
        /// Gets or sets the current list item.
        /// </summary>
        /// <value>
        /// The current list item.
        /// </value>
        [Description( "Current list item." )]
        [Category( "MASAS Publication Action" )]
        [Browsable( true )]
        [DesignerSerializationVisibility( DesignerSerializationVisibility.Visible )]
        public int ListItem
        {
            get
            {
                return ( ( int )( base.GetValue( PublishingAction.ListItemProperty ) ) );
            }
            set
            {
                base.SetValue( PublishingAction.ListItemProperty, value );
            }
        }

        /// <summary>
        /// Gets or sets the hub list id.
        /// </summary>
        /// <value>
        /// The hub list id.
        /// </value>
        [Description( "Hub list ID." )]
        [Category( "MASAS Publication Action" )]
        [Browsable( true )]
        [DesignerSerializationVisibility( DesignerSerializationVisibility.Visible )]
        public string HubListId
        {
            get
            {
                return ( ( string )( base.GetValue( PublishingAction.HubListIdProperty ) ) );
            }
            set
            {
                base.SetValue( PublishingAction.HubListIdProperty, value );
            }
        }

        /// <summary>
        /// Gets or sets the hub item.
        /// </summary>
        /// <value>
        /// The hub item.
        /// </value>
        [Description( "MASAS Hub Connection list item." )]
        [Category( "MASAS Publication Action" )]
        [Browsable( true )]
        [DesignerSerializationVisibility( DesignerSerializationVisibility.Visible )]
        public int HubListItem
        {
            get
            {
                return ( ( int )( base.GetValue( PublishingAction.HubListItemProperty ) ) );
            }
            set
            {
                base.SetValue( PublishingAction.HubListItemProperty, value );
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether [publication result].
        /// </summary>
        /// <value>
        ///   <c>true</c> if [publication result]; otherwise, <c>false</c>.
        /// </value>
        [Description( "Publication Result." )]
        [Category( "MASAS Publication Action" )]
        [Browsable( true )]
        [DesignerSerializationVisibility( DesignerSerializationVisibility.Visible )]
        public bool PublicationResult
        {
            get
            {
                return ( ( bool )( base.GetValue( PublishingAction.PublicationResultProperty ) ) );
            }
            set
            {
                base.SetValue( PublishingAction.PublicationResultProperty, value );
            }
        }

        /// <summary>
        /// Gets or sets the publication result MSG.
        /// </summary>
        /// <value>
        /// The publication result MSG.
        /// </value>
        [Description( "Publication Result Message." )]
        [Category( "MASAS Publication Action" )]
        [Browsable( true )]
        [DesignerSerializationVisibility( DesignerSerializationVisibility.Visible )]
        public string PublicationResultMsg
        {
            get
            {
                return ( ( string )( base.GetValue( PublishingAction.PublicationResultMsgProperty ) ) );
            }
            set
            {
                base.SetValue( PublishingAction.PublicationResultMsgProperty, value );
            }
        }

        /// <summary>
        /// Called by the workflow runtime to execute an activity.
        /// </summary>
        /// <param name="executionContext">The <see cref="T:System.Workflow.ComponentModel.ActivityExecutionContext"/> to associate with this <see cref="T:System.Workflow.ComponentModel.Activity"/> and execution.</param>
        /// <returns>
        /// The <see cref="T:System.Workflow.ComponentModel.ActivityExecutionStatus"/> of the run task, which determines whether the activity remains in the executing state, or transitions to the closed state.
        /// </returns>
        protected override ActivityExecutionStatus Execute( ActivityExecutionContext executionContext )
        {
            PublicationResult = false;

            // Get the SharePoint Service for logging any errors...
            ISharePointService wfService = executionContext.GetService<ISharePointService>();

            // Get the current list and item...
            Guid        listGuid    = new Guid( ListId );
            SPList      curList     = __Context.Web.Lists[listGuid];
            SPListItem  curItem     = curList.GetItemById( ListItem );

            // Validate the data in the list item...
            if( !IsPublicationDataValid( curItem ) )
            {
                PublicationResult = false;

                wfService.LogToHistoryList( executionContext.ContextGuid,
                                            SPWorkflowHistoryEventType.WorkflowComment,
                                            0,
                                            TimeSpan.Zero,
                                            "Error",
                                            PublicationResultMsg,
                                            string.Empty );

                return ActivityExecutionStatus.Closed;
            }

            // Get the MASAS Hub that we want to push the data into...
            Hub hub = GetPublicationHub();

            if( hub != null )
            {
                // Convert the current item into a Publication entry...
                PublicationEntry pubEntry = ItemToPublication( curItem );

                // Create the connection to the Publication Service...
                PublicationServiceClient client = new PublicationServiceClient();

                // We either need to create a new publication or update an existing one.
                // If the "SourceItemID" exists, then we update, otherwise we create.
                FeedEntry feedEntry = null;

                try
                {
                    if( curItem[_masasSourceItemIDField] != null )
                    {
                        string feedEntryId = curItem[_masasSourceItemIDField].ToString();
                        feedEntry = client.UpdateEntry( hub, feedEntryId, pubEntry );
                    }
                    else
                    {
                        feedEntry = client.PublishEntry( hub, pubEntry );
                    }

                    // Close the Publication connection.
                    client.Close();
                }
                catch( Exception ex )
                {
                    // A problem with the Publication Service occured...
                    client.Abort();
                    PublicationResultMsg = string.Format( "Publication Service exception: {0}", ex.Message );
                }

                if( feedEntry != null )
                {
                    // Update the SharePoint item with the published data...
                    UpdateItem( curItem, feedEntry );

                    // Success...
                    PublicationResult = true;
                    PublicationResultMsg = "Item published to MASAS Hub.";

                    if( pubEntry.Attachments.Length != feedEntry.Attachments.Length )
                    {
                        PublicationResultMsg += "  ";
                        PublicationResultMsg += feedEntry.Attachments.Length.ToString();
                        PublicationResultMsg += " of ";
                        PublicationResultMsg += pubEntry.Attachments.Length.ToString();
                        PublicationResultMsg += " attachments where published.";
                    }

                }
            }

            if( PublicationResult == false )
            {
                wfService.LogToHistoryList( executionContext.ContextGuid,
                                            SPWorkflowHistoryEventType.WorkflowComment,
                                            0,
                                            TimeSpan.Zero,
                                            "Error",
                                            PublicationResultMsg,
                                            string.Empty );
            }

            return ActivityExecutionStatus.Closed;
        }

        /// <summary>
        /// Called when an exception is raised within the context of the execution of this instance.
        /// </summary>
        /// <param name="executionContext">The <see cref="T:System.Workflow.ComponentModel.ActivityExecutionContext"/> for this instance.</param>
        /// <param name="exception">The <see cref="T:System.Exception"/> that caused this fault.</param>
        /// <returns>
        /// The <see cref="T:System.Workflow.ComponentModel.ActivityExecutionStatus"/> that results from an attempt to cancel this instance.
        /// </returns>
        /// <exception cref="T:System.ArgumentNullException">
        ///   <paramref name="executionContext"/> is a null reference (Nothing in Visual Basic).</exception>
        ///   
        /// <exception cref="T:System.ArgumentNullException">
        ///   <paramref name="exception"/> is a null reference (Nothing in Visual Basic).</exception>
        protected override ActivityExecutionStatus HandleFault( ActivityExecutionContext executionContext, Exception exception )
        {
            // Get the SharePoint service...
            ISharePointService wfService = executionContext.GetService<ISharePointService>();

            string errorMsg = string.Format( "WorkFlow Failed: {0}", exception.Message );

            // Write the exception to the workflow history...
            wfService.LogToHistoryList( executionContext.ContextGuid,
                                        SPWorkflowHistoryEventType.WorkflowComment,
                                        0,
                                        TimeSpan.Zero,
                                        "Error",
                                        errorMsg,
                                        string.Empty );

            // Set the out parameters as needed...
            PublicationResultMsg = errorMsg;
            PublicationResult = false;

            return ActivityExecutionStatus.Closed;
        }

        /// <summary>
        /// Updates the SharePoint item.
        /// </summary>
        /// <param name="curItem">The SharePoint item.</param>
        /// <param name="feedEntry">The feed entry.</param>
        private void UpdateItem( SPListItem curItem, FeedEntry feedEntry )
        {
            curItem[_masasSourceItemIDField] = feedEntry.Identifier;
            curItem[_masasLastUpdatedDTGField] = feedEntry.LastUpdated;
            
            curItem.Update();
        }


        private bool IsPublicationDataValid( SPListItem curItem )
        {
            bool retValue = true;
            List<string> missingFields = new List<string>();

            if( curItem[_masasMessageStatusField] == null )
            {
                missingFields.Add( _masasMessageStatusField );
            }

            if( curItem[_masasSymbolIDField] == null )
            {
                missingFields.Add( _masasSymbolIDField );
            }

            if( curItem[_masasExpirationField] == null )
            {
                missingFields.Add( _masasExpirationField );
            }

            if( curItem[_masasLatitudeField] == null )
            {
                missingFields.Add( _masasLatitudeField );
            }

            if( curItem[_masasLongitudeField] == null )
            {
                missingFields.Add( _masasLongitudeField );
            }

            if( missingFields.Count > 0 )
            {
                retValue = false;
                PublicationResultMsg = _msgMissingData + string.Join( ", ", missingFields.ToArray() ) + ".";
            }

            return retValue;
        }

        /// <summary>
        /// Convert a SharePoint item to a publication entry.
        /// </summary>
        /// <param name="curItem">The SharePoint item.</param>
        /// <returns>A new populated publication entry</returns>
        private PublicationEntry ItemToPublication( SPListItem curItem )
        {
            // Create the publication entry...
            PublicationEntry pubEntry = new PublicationEntry();

            // Copy the data over...
            pubEntry.Title      = curItem.Title;

            pubEntry.Status     = ( StatusTypes )Enum.Parse( typeof( StatusTypes ), curItem[_masasMessageStatusField].ToString(), true );
            pubEntry.Icon       = curItem[_masasSymbolIDField].ToString();
            pubEntry.Expiration = ( DateTime )curItem[_masasExpirationField];

            try {
                pubEntry.Categories = new CategoryTypes[1] { (CategoryTypes)Enum.Parse( typeof( CategoryTypes ), curItem[_masasCategoryField].ToString() ) };
            }
            catch
            {
            }

            if( curItem[_masasSummaryField] != null )
            {
                pubEntry.Summary = curItem[_masasSummaryField].ToString();
            }

            if( curItem[_masasContentField] != null )
            {
                pubEntry.Content = curItem[_masasContentField].ToString();
            }

            GeoPoint geoPoint   = new GeoPoint();
            geoPoint.Latitude   = Convert.ToDouble( curItem[_masasLatitudeField].ToString() );
            geoPoint.Longitude  = Convert.ToDouble( curItem[_masasLongitudeField].ToString() );
            pubEntry.Geometries = new Geometry[1] { geoPoint };

            pubEntry.Attachments = GetItemAttachments( curItem ).ToArray();

            return pubEntry;
        }

        private List<Attachment> GetItemAttachments( SPListItem curItem )
        {
            SPAttachmentCollection itemAttachments = curItem.Attachments;

            List<Attachment> pubAttachments = new List<Attachment>();

            foreach( string fileName in itemAttachments )
            {
                SPFile file = __Context.Web.GetFile( itemAttachments.UrlPrefix + fileName );
                byte[] dataBlob = file.OpenBinary();
                string base64str = System.Convert.ToBase64String( dataBlob, 0, dataBlob.Length );

                Attachment newAttachment = new Attachment();
                newAttachment.FileName = fileName;
                newAttachment.Title = fileName.Split( '.' )[0];
                newAttachment.Base64 = base64str;

                pubAttachments.Add( newAttachment );
            }

            return pubAttachments;
        }

        /// <summary>
        /// Gets the publication hub.
        /// </summary>
        /// <returns></returns>
        private Hub GetPublicationHub()
        {
            Hub         hub     = null;
            SPList      curList = null;
            SPListItem  item    = null;
                        
            // Look for the Hub list...
            try
            {
                curList = __Context.Web.Lists[new Guid( HubListId )];
            }
            catch( Exception /*e*/ )
            {
                try
                {
                    curList = __Context.Web.Lists[HubListId];
                }
                catch( Exception /*e*/ )
                {
                    PublicationResultMsg = _msgNoHubListFound;
                    return null;
                }
            }

            // Validate the list given is what we expected, otherwise the Hub Item ID won't be good...
            if( curList.Title != _masasHubConnectionsListName )
            {
                PublicationResultMsg = _msgInvalidHubList;
                return null;
            }

            // Find the requested Hub...
            try
            {
                item = curList.GetItemById( HubListItem );
            }
            catch( Exception /*e*/ )
            {
                PublicationResultMsg = _msgNoHubFound;
                return null;
            }

            // Validate the Hub has write access..
            if( item[_masasAccessRightsField].ToString().Contains( "Write" ) )
            {
                // Populate the hub...
                hub = new Hub();
                hub.Name = item[_spTitleField].ToString();

                SPFieldUrlValue urlValue = new SPFieldUrlValue( item[_masasURIField].ToString() );
                hub.URI = urlValue.Url;
                hub.Token = item[_masasTokenField].ToString();
            }
            else
            {
                PublicationResultMsg = _msgReadOnlyHubFound;
            }

            // Done...
            return hub;
        }

    }

}
